#include <string.h>
#include <stdio.h>
#include "connbuffer.h"

typedef struct connbuffer_t
{
	char* buffer;
	int buffer_size;
	int read_pos;
	int write_pos;
	int drift_threshold;
}connbuffer_t;

struct connbuffer_t* connbuffer_init(int buffer_size, int threshold_hint)
{
	if(buffer_size <= 0 || threshold_hint <= 0)
		return NULL;
	
	connbuffer_t* connbuffer = (connbuffer_t*)CALLOC(1, sizeof(struct connbuffer_t));
	assert(connbuffer);
	connbuffer->buffer_size = buffer_size;
	connbuffer->read_pos = 0;
	connbuffer->write_pos = 0;
	connbuffer->drift_threshold = threshold_hint;
	connbuffer->buffer = (char*)MALLOC(buffer_size);
	assert(connbuffer->buffer);
	return connbuffer;
}

int connbuffer_release(struct connbuffer_t* connbuffer)
{
	if(connbuffer)
	{
		FREE(connbuffer->buffer);
		FREE(connbuffer);
	}
	return 0;
}

// read data in buffer to dest
// return: read bytes
int connbuffer_read(struct connbuffer_t* connbuffer, char* dest, int len)
{
	if(!connbuffer || !dest || len < 0)
		return -1;
	if(0 == len)
		return 0;

	if(len > connbuffer_read_len(connbuffer))
		return connbuffer_read(connbuffer, dest, connbuffer_read_len(connbuffer));

	memcpy(dest, connbuffer_read_buffer(connbuffer), len);
	return connbuffer_read_nocopy(connbuffer, len);
}

int connbuffer_read_nocopy(struct connbuffer_t* connbuffer, int len)
{
	if(!connbuffer || len < 0)
		return -1;

	if(len > connbuffer_read_len(connbuffer))
		return connbuffer_read_nocopy(connbuffer, connbuffer_read_len(connbuffer));

	connbuffer->read_pos += len;
	assert(connbuffer->write_pos >= connbuffer->read_pos);

	// check drift threshold
	if(connbuffer->read_pos > connbuffer->drift_threshold)
	{
		const char* shift = connbuffer_read_buffer(connbuffer);
		// memcpy faster than memmove, cpu 30% efficiency up
		memcpy(connbuffer->buffer, shift, connbuffer->write_pos - connbuffer->read_pos);
		connbuffer->write_pos -= connbuffer->read_pos;
		connbuffer->read_pos = 0;
	}

	return len;
}

// only read and not pick out
// return: read bytes
int connbuffer_peek(struct connbuffer_t* connbuffer, char* dest, int len)
{
	if(!connbuffer || len < 0 || !dest)
		return -1;
	if(0 == len)
		return 0;
	
	if(len > connbuffer_read_len(connbuffer))
	{
		return connbuffer_peek(connbuffer, dest, connbuffer_read_len(connbuffer));
	}
	memcpy(dest, connbuffer_read_buffer(connbuffer), len);
	return len;
}

// write data from src to connbuffer
// return: write bytes
int connbuffer_write(struct connbuffer_t* connbuffer, const char* src, int len)
{
	if(!connbuffer || !src || len < 0)
		return -1;
	if(0 == len)
		return 0;

	if(connbuffer_write_len(connbuffer) < len)
	{
		return connbuffer_write(connbuffer, src, connbuffer_write_len(connbuffer));
	}
	memcpy(connbuffer_write_buffer(connbuffer), src, len);
	return connbuffer_write_nocopy(connbuffer, len);
}

int connbuffer_write_nocopy(struct connbuffer_t* connbuffer, int len)
{
	if(connbuffer_write_len(connbuffer) < len)
	{
		return connbuffer_write_nocopy(connbuffer, connbuffer_write_len(connbuffer));
	}

	if(len < 0)
		return -1;
	connbuffer->write_pos += len;
	assert(connbuffer->write_pos <= connbuffer->buffer_size);
	return len;
}

int connbuffer_reset(struct connbuffer_t* connbuffer)
{
	if(!connbuffer)
		return -1;
	connbuffer->read_pos = connbuffer->write_pos = 0;
	return 0;
}

const char* connbuffer_debug(struct connbuffer_t* connbuffer)
{
	if(!connbuffer)
		return NULL;
	
	static char debug_str[64];
	memset(debug_str, 0, sizeof(debug_str));
	snprintf(debug_str, sizeof(debug_str),
		"size=%d, read_pos=%d, write_pos=%d", 
		connbuffer->buffer_size,
		connbuffer->read_pos,
		connbuffer->write_pos);
	return debug_str;
}

char* connbuffer_read_buffer(struct connbuffer_t* connbuffer)
{
	return connbuffer->buffer + connbuffer->read_pos;
}
  
char* connbuffer_write_buffer(struct connbuffer_t* connbuffer)
{
	return connbuffer->buffer + connbuffer->write_pos;
}

int connbuffer_read_len(struct connbuffer_t* connbuffer)
{
	if(!connbuffer)
		return -1;
	
	return connbuffer->write_pos > connbuffer->read_pos ? 
		(connbuffer->write_pos - connbuffer->read_pos) : 0;
}

int connbuffer_write_len(struct connbuffer_t* connbuffer)
{
	if(!connbuffer)
		return -1;
	return connbuffer->buffer_size - connbuffer->write_pos;
}

